<?php
/**
* Generic ABM class for faster modeling.
*/

class ABM extends CustomModel
{

    private $table;
    private $perPage = 10;

    public function __construct($tableName)
    {
        parent::__construct();
        if ((empty($tableName)) || (!$this->db->tableExists($tableName)))
        {
            die("No table for abm!");
        }
        else
        {
            $this->table = $tableName;
        }
    }

    public function insert($data, $expectingInsertId = true)
    {
        $insert = $this->db->insertInto($this->table)->values($data)->execute($expectingInsertId);
        return $insert;
    }

    public function getWithJoin($filters = false, $joins = array(), $orderByField = false, $limit = false, $indexPk = true)
    {
        if (empty($filters) || empty($joins)) return false;
        else
        {
            foreach ($joins as $tableName => $join)
            {
                if ($this->db->tableExists($tableName))
                {
                    if (!empty($join['type']))
                    {
                        $joins[$tableName] = array(
                            'on'   => $this->buildFilter($join['on']),
                            'type' => $join['type']
                        );
                    }
                    else
                    {
                        $joins[$tableName] = $this->buildFilter($join);
                    }
                }
                else
                {
                    return false;
                }
            }
            // Table pk
            if ($indexPk)
            {
                $tablePk = $this->db->getTablePk($this->table);
            }
            else
            {
                $tablePk = false;
            }
            // Check order by
            if ($orderByField) $orderByField = $this->sanitizeOrderBy($orderByField);
            // Check if limit
            $limit = intval($limit);
            // Prepare Base
            $selectFields = array($this->table.'.*');
            $myTypes = $this->db->getTableFields($this->table);
            foreach ($joins as $table => $on)
            {
                // Check field name redundance
                $redundance = false;
                $tableTypes = $this->db->getTableFields($table);
                foreach ($tableTypes as $fieldName => $foo)
                {
                    if (isset($myTypes[$fieldName]))
                    {
                        $redundance = true;
                        break;
                    }
                }
                // Fields
                if (!$redundance)
                {
                    $selectFields[]=$table.'.*';
                }
                else
                {
                    foreach ($tableTypes as $fieldName => $foo)
                    {
                        $selectFields[]=$table.'.'.$fieldName.' AS '."'".$table.'.'.$fieldName."'";
                    }
                }
            }
            $pageResults = call_user_func_array(array($this->db, 'select'), $selectFields);
            $pageResults = $pageResults->from($this->table);
            // Set joins
            foreach ($joins as $table => $join)
            {
                if (is_array($join) && (!empty($join['type'])))
                {
                    $type = ((strtoupper($join['type']) == 'RIGHT') || (strtoupper($join['type']) == 'LEFT'))?strtolower($join['type']).'Join':'join';
                    $pageResults = $pageResults->$type($table)->on($join['on']);
                }
                else
                {
                    $pageResults = $pageResults->join($table)->on($join);
                }
            }
            // Set where
            $pageResults = $pageResults->where($this->buildFilter($filters));
            // Prepare according to order
            if ($orderByField)
            {
                $pageResults = $pageResults->orderBy($orderByField);
            }
            if ($limit)
            {
                $pageResults = $pageResults->limit($limit);
            }
            return $pageResults->get(false, $tablePk);
        }
    }

    public function get($filters = false, $orderByField = false, $limit = false, $indexPk = true)
    {
        if (empty($filters)) return false;
        else
        {
            // Table pk
            if ($indexPk)
            {
                $tablePk = $this->db->getTablePk($this->table);
            }
            else
            {
                $tablePk = false;
            }
            // Check order by
            if ($orderByField) $orderByField = $this->sanitizeOrderBy($orderByField);
            // Check if limit
            $limit = intval($limit);
            if ($orderByField)
            {
                if ($limit)
                {
                    $pageResults = $this->db->select('*')->from($this->table)->where($this->buildFilter($filters))->orderBy($orderByField)->limit($limit)->get(false, $tablePk);
                }
                else
                {
                    $pageResults = $this->db->select('*')->from($this->table)->where($this->buildFilter($filters))->orderBy($orderByField)->get(false, $tablePk);
                }
            }
            else
            {
                if ($limit)
                {
                    $pageResults = $this->db->select('*')->from($this->table)->where($this->buildFilter($filters))->limit($limit)->get(false, $tablePk);
                }
                else
                {
                    $pageResults = $this->db->select('*')->from($this->table)->where($this->buildFilter($filters))->get(false, $tablePk);
                }
            }
            return $pageResults;
        }

    }

    public function getPaginatedWithJoin($pageNumber, $filters = false, $joins, $perPage = false, $orderByField = false, $force = false)
    {
        return $this->getPaginated($pageNumber, $filters, $perPage, $orderByField, $force, $joins);
    }

    public function getPaginated($pageNumber, $filters = false, $perPage = false, $orderByField = false, $force = false, $joins = false)
    {
        $tablePk = $this->db->getTablePk($this->table);
        // Joins
        if (!empty($joins))
        {
            foreach ($joins as $tableName => $join)
            {
                if ($this->db->tableExists($tableName))
                {
                    if (!empty($join['type']))
                    {
                        $joins[$tableName] = array(
                            'on'   => $this->buildFilter($join['on']),
                            'type' => $join['type']
                        );
                    }
                    else
                    {
                        $joins[$tableName] = $this->buildFilter($join);
                    }
                }
                else
                {
                    return false;
                }
            }
        }
        // Check if in cache
        $total =  $this->getPaginationTotal($this->buildPaginationHash($filters));
        // Get and build cache
        if ((empty($total)) || ($force))
        {
            if (!empty($joins))
            {
                // Prepare Base
                $selectFields = array($this->table.'.*');
                $myTypes = $this->db->getTableFields($this->table);
                foreach ($joins as $table => $on)
                {
                    // Check field name redundance
                    $redundance = false;
                    $tableTypes = $this->db->getTableFields($table);
                    foreach ($tableTypes as $fieldName => $foo)
                    {
                        if (isset($myTypes[$fieldName]))
                        {
                            $redundance = true;
                            break;
                        }
                    }
                    // Fields
                    if (!$redundance)
                    {
                        $selectFields[]=$table.'.*';
                    }
                    else
                    {
                        foreach ($tableTypes as $fieldName => $foo)
                        {
                            $selectFields[]=$table.'.'.$fieldName.' AS '."'".$table.'.'.$fieldName."'";
                        }
                    }
                }
                $baseCount = call_user_func_array(array($this->db, 'select'), array('COUNT('.$tablePk.') AS total'));
                $baseCount = $baseCount->from($this->table);
                // Set joins
                foreach ($joins as $table => $join)
                {
                    if (is_array($join) && (!empty($join['type'])))
                    {
                        $type = ((strtoupper($join['type']) == 'RIGHT') || (strtoupper($join['type']) == 'LEFT'))?strtolower($join['type']).'Join':'join';
                        $baseCount = $baseCount->$type($table)->on($join['on']);
                    }
                    else
                    {
                        $baseCount = $baseCount->join($table)->on($join);
                    }
                }
                $baseCount = $baseCount->where($this->buildFilter($filters))->get(true);
            }
            else
            {
                $baseCount = $this->db->select('COUNT('.$tablePk.') AS total')->from($this->table)->where($this->buildFilter($filters))->get(true);
            }
            if ($baseCount)
            {
                $this->setPaginationTotal($this->buildPaginationHash($filters), $baseCount['total']);
                $total = $baseCount['total'];
            }
            else
            {
                return false;
            }
        }
        // Check order by
        if ($orderByField) $orderByField = $this->sanitizeOrderBy($orderByField);
        // Get for current page
        $perPage = (empty($perPage))?$this->perPage:$perPage;
        $totalPages = ceil($baseCount['total'] / $perPage);
        $pageNumber = ($pageNumber > $totalPages)?1:$pageNumber;
        $offset     = (($pageNumber-1)*$perPage);
        // Is join?
        if (!empty($joins))
        {
            $pageResults = call_user_func_array(array($this->db, 'select'), $selectFields);
            $pageResults = $pageResults->from($this->table);
            // Set joins
            foreach ($joins as $table => $join)
            {
                if (is_array($join) && (!empty($join['type'])))
                {
                    $type = ((strtoupper($join['type']) == 'RIGHT') || (strtoupper($join['type']) == 'LEFT'))?strtolower($join['type']).'Join':'join';
                    $pageResults = $pageResults->$type($table)->on($join['on']);
                }
                else
                {
                    $pageResults = $pageResults->join($table)->on($join);
                }
            }
            $pageResults = $pageResults->where($this->buildFilter($filters));
            if ($orderByField)
            {
                $pageResults = $pageResults->orderBy($orderByField)->limit($offset, $perPage)->get(false, $tablePk);
            }
            else
            {
                $pageResults = $pageResults>limit($offset, $perPage)->get(false, $tablePk);
            }
        }
        else
        {
            if ($orderByField)
            {
                $pageResults = $this->db->select('*')->from($this->table)->where($this->buildFilter($filters))->orderBy($orderByField)->limit($offset, $perPage)->get(false, $tablePk);
            }
            else
            {
                $pageResults = $this->db->select('*')->from($this->table)->where($this->buildFilter($filters))->limit($offset, $perPage)->get(false, $tablePk);
            }
        }

        if ($pageResults)
        {
            return array(
                'page'       => $pageNumber,
                'totalPages' => $totalPages,
                'results'    => $pageResults
            );
        }
        else
        {
            return false;
        }
    }

    public function update($filter, $fields)
    {
        $filter = $this->buildFilter($filter);
        if ($filter)
        {
            if (!empty($fields) && is_array($fields))
            {
                return $this->db->update($this->table)->set($fields)->where($filter)->execute();
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }
    }

    private function sanitizeOrderBy($orderBy)
    {
        if (is_array($orderBy))
        {
            return implode(',', $orderBy);
        }
        else
        {
            return $orderBy;
        }
    }

    private function buildFilter($filter)
    {
        if ((!empty($filter)) && (is_array($filter)))
        {
            $filterObj = false;
            foreach ($filter as $field => $value)
            {
                if (is_array($value))
                {
                    $auxFilter = false;
                    foreach($value as $auxValue)
                    {
                        $auxFilter2 = new dbFilter($field, '=', $auxValue);
                        if (!$auxFilter)
                        {
                            $auxFilter = $auxFilter2;
                        }
                        else
                        {
                            $auxFilter = new dbFilter($auxFilter, 'OR', $auxFilter2);
                        }
                    }
                    if ($filterObj)
                    {
                        $filterObj  = new dbFilter($auxFilter, 'AND', $filterObj);
                    }
                    else
                    {
                        $filterObj = $auxFilter;
                    }
                }
                else
                {
                    if ($filterObj)
                    {
                        $filterObjA = new dbFilter($field, '=', $value);
                        $filterObj  = new dbFilter($filterObjA, 'AND', $filterObj);
                    }
                    else
                    {
                        $filterObj = new dbFilter($field, '=', $value);
                    }
                }
            }
            return $filterObj;
        }
        else
        {
            if (is_a($filter, 'dbFilter'))
            {
                return $filter;
            }
            else
            {
                return false;
            }
        }
    }

    private function setPaginationTotal($hash, $total)
    {
        return $this->session->set($hash, $total);
    }

    private function getPaginationTotal($hash)
    {
        return $this->session->get($hash);
    }

    private function buildPaginationHash($filtersArray = false)
    {
        $build = $this->table.':::____pagination_';
        if (!empty($filtersArray))
        {
            $build = serialize($filtersArray);
        }
        return md5(sha1($build));
    }

}
?>